import { NextFunction, Request, Response } from 'express';
import { MongoClient } from 'mongodb';
import cryptoRandomString from 'crypto-random-string';
import { ServerConfig } from './ServerConfig';
import moment from 'moment';
import { UserId } from '../models/user';

export interface Token {
	token: string;
	expireOn: Date;
	username: UserId;
}

export class TokenPool {
	constructor(config: ServerConfig, mongo: MongoClient) {
		this.config = config;
		this.mongo = mongo;
	}

	public async checkToken(req: Request, res: Response, next: NextFunction) {
		let now = new Date();
		let token = req.body['token'] || req.query['token'];
		if (token == null) {
			res.status(400);
			res.send({
				reason: 'token required'
			});
		} else {
			if (this.config.debug) {
				if (token == this.config.debugToken) {
					next();
					return;
				}
			}
			let out = await this.mongo.db().collection<Token>(this.config.token.collection).findOne({ token: token });
			if (out == null) {
				res.status(403);
				res.send({
					reason: 'invalid token'
				});
			} else {
				if (moment(out.expireOn).isBefore(now)) {
					res.status(403);
					res.send({
						reason: 'token expired'
					});
					await this.mongo.db().collection(this.config.token.collection).deleteOne({ token: token });
				} else {
					next();
				}
			}
		}
	}

	public async dispenseToken(user: UserId): Promise<Token> {
		const token = {
			token: cryptoRandomString({ length: 32 }),
			expireOn: moment(new Date()).add(this.config.token.expiration, 'millisecond').toDate(),
			username: user
		};
		// XXX: check error here
		await this.mongo.db().collection(this.config.token.collection).insertOne(token);
		return token;
	}

	public async revokeToken(token: string) {
		await this.mongo.db().collection(this.config.token.collection).deleteOne({ token: token });
	}

	public sweepToken() {
		return this.mongo.db().collection(this.config.token.collection).deleteMany({ expireOn: { $lt: new Date() }});
	}

	private config: ServerConfig;
	private mongo: MongoClient;
}
